#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/types.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/module.h>
#include <asm/uaccess.h>

MODULE_LICENSE("GPL");
MODULE_AUTHOR("Anndse");

static int __init mychr_dev_init(void);
static void __exit module_close(void);

static int device_open(struct inode*, struct file*);
static int device_release(struct inode*, struct file*);
static ssize_t device_read(struct file*, char*, size_t, loff_t*);
static ssize_t device_write(struct file*, const char*, size_t, loff_t*);

#define SUCCESS 0
#define DEVICE_NAME "chardev"
#define PTR_LEN    10

struct mychr_dev{
    void *msg_ptr[PTR_LEN];
    unsigned int len[PTR_LEN];
    struct cdev *cdev;
};

static struct file_operations chr_fops = {
    .owner = THIS_MODULE,
    .read = device_read,
    .write = device_write,
    .open = device_open,
    .release = device_release
};

static struct mychr_dev mychr_dev;
static int chr_major = 0, chr_minor = 0;
static int Device_Open = 0;

static int __init mychr_dev_init(void)
{
    int n = 0;
    int err;

    dev_t dev = MKDEV(chr_major, chr_minor);
    printk(KERN_ALERT "Initing.....\n");
    (&mychr_dev)->cdev = cdev_alloc();
    if((&mychr_dev)->cdev == NULL)
	return -1;
    if(alloc_chrdev_region(&dev, 0, 1, DEVICE_NAME)){
	printk(KERN_ALERT "Register chardev error\n");
	return -1;
    }

    for(; n < PTR_LEN; n++){
	(&mychr_dev)->msg_ptr[n] = NULL;
	(&mychr_dev)->len[n] = 0;
    }
    cdev_init((&mychr_dev)->cdev, &chr_fops);
    (&mychr_dev)->cdev->owner = THIS_MODULE;
    err = cdev_add((&mychr_dev)->cdev, dev, 1);
    
    if(err){
	printk(KERN_ALERT "Error %d adding chardev 0\n", err);
	return -1;
    }

    chr_major = MAJOR(dev);
    chr_minor = MINOR(dev);
    printk(KERN_ALERT "I was assigned major number %d. To talk to\n", chr_major);
    printk(KERN_ALERT "the driver, create a dev file with \n");
    printk(KERN_ALERT "'mknod /dev/%s c %d %d'.\n", DEVICE_NAME, chr_major, chr_minor);
    printk(KERN_ALERT "Try various minor numbers. Try to cat and echo to\n");
    printk(KERN_ALERT "the device file.\n");  
    printk(KERN_ALERT "Remove the device file and module when done.\n");

    return 0;
}

static void __exit module_close(void)
{
    int i = 0;
    dev_t dev = MKDEV(chr_major, chr_minor);
    
    unregister_chrdev_region(dev, 1);
    cdev_del(mychr_dev.cdev);
    
    for(; i < PTR_LEN; i++){
	if(mychr_dev.msg_ptr[i] != NULL){
	    kfree(mychr_dev.msg_ptr[i]);
	    mychr_dev.msg_ptr[i] = NULL;
	    mychr_dev.len[i] = 0;
	}
    }
}

static int device_open(struct inode *inode, struct file *file)
{
    if(Device_Open){
	printk(KERN_ALERT "Anther process open the char device!\n");
	return -EBUSY;
    }

    Device_Open++;
    try_module_get(THIS_MODULE);

    printk(KERN_ALERT "You have open the char device!\n");

    return SUCCESS;
}

static int device_release(struct inode* inode, struct file* file)
{
    Device_Open--;
    module_put(THIS_MODULE);
    return 0;
}

static ssize_t device_write(struct file* filp, const char __user *usrbuff,
			   size_t length, loff_t* offset)
{
    unsigned long len = 0; 
    int retval = 0;
    int iptr = 0;

    for(; iptr < PTR_LEN; iptr++){
	if(mychr_dev.msg_ptr[iptr] == NULL){
	    break;
	}
    }
    if(iptr >= PTR_LEN){
	//本该挂起，直到有空余的地方再被唤醒，但现在只是返回零
	goto out;
    }

    if(length > 0){
	mychr_dev.msg_ptr[iptr] = kmalloc(length+1, GFP_KERNEL);
	if(mychr_dev.msg_ptr == NULL){
	    retval = -EFAULT;
	    goto out;
	}
	memset(mychr_dev.msg_ptr[iptr], 0, length+1);
	len = copy_from_user(mychr_dev.msg_ptr[iptr], usrbuff, length);
	if(len){
	    retval = -EFAULT;
	    goto out;
	}
	retval = length;
    }
    printk(KERN_ALERT "Have written: %s\n", mychr_dev.msg_ptr[iptr]);

out:
    return retval;
}

static ssize_t device_read(struct file* filp,
			    char* usrbuff,
			    size_t length,
			    loff_t* offset)
{
    int retval = 0;
    int i = 0;
    unsigned long len;

    while(mychr_dev.msg_ptr[i] == NULL)
	i++;
    if(i >= PTR_LEN){
	printk(KERN_ALERT "No data to read!\n");
	retval = -1;
	goto out;
    }
    len = mychr_dev.len[i];

    if(copy_to_user(usrbuff, mychr_dev.msg_ptr[i], len)){
	retval = -EFAULT;
	goto out;
    }
    printk(KERN_ALERT "read data: %s\n", mychr_dev.msg_ptr[i]);
    kfree(mychr_dev.msg_ptr[i]);
    mychr_dev.msg_ptr[i] = NULL;
    mychr_dev.len[i] = 0;
    retval = len;

out:
    return retval;
}

module_init(mychr_dev_init);
module_exit(module_close);

